﻿using System;
using System.Globalization;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using BankService.App_Code;
using ClientBankWPF.Entities;
using ClientBankWPF.Service;
using CryptoModule;

namespace BankService
{
    public class BankService : IBankService
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        //Добавление платежного поручения в базу банка
        public StateAndReason AddPaymentOrder(PaymentOrder paymentOrder, byte[] eds)
        {
            var result = new StateAndReason();
            if (paymentOrder == null)
                throw new ArgumentNullException("paymentOrder");
            if (eds == null)
                throw new ArgumentNullException("eds");

            var das = new DataAccessService();

            if (paymentOrder.PayerUNP <= 0)
                throw new ArgumentException("Wrong payer UNP!");

            var client = das.GetClientByUNP(paymentOrder.PayerUNP);
            if (client == null)
                throw new ApplicationException("Can't find payer info!");

            int? maxNumb = GetMaxPaymentOrderNumber(paymentOrder.PayerUNP);
            if (!maxNumb.HasValue) maxNumb = 0;
            var hash = MD5Crypto.MD5Encode(GeneratePaymentOrderFieldsHash(paymentOrder));
            var rsa = new RSASignature();
            State stateSucces = das.GetState("принято");
            State stateDeny = das.GetState("отклонено");
            if (rsa.VerifySignature(hash, eds, client.OpenKey))
            {
                Deposit deposit = das.GetDeposit(paymentOrder.PayerDepositNumber);
                if (deposit.Sum - paymentOrder.Sum < 0)
                {
                    result.PaymentOrderState = stateDeny;
                    result.Reason = "На счету недостаточно средств.";
                }
                else
                {
                    if (paymentOrder.BenefBankCode == 100)
                    {
                        Deposit benefDeposit = das.GetDeposit(paymentOrder.BenefDepNum);
                        if (benefDeposit == null)
                        {
                            result.PaymentOrderState = stateDeny;
                            result.Reason = "Указанный счёт не существует";
                            return result;
                        }
                        Client benefClient = das.GetClientByUNP(paymentOrder.BenefUNP);
                        if (benefClient == null)
                        {
                            result.PaymentOrderState = stateDeny;
                            result.Reason = "Организации с указанным УНП не существует";
                            return result;
                        }
                        if (benefClient.Name != paymentOrder.Benefeciar)
                        {
                            result.PaymentOrderState = stateDeny;
                            result.Reason = "За данным УНП числится другая организация: " + benefClient.Name;
                            return result;
                        }
                        bool isContains = benefClient.Deposits.Any(deposit1 => deposit1.Number == benefDeposit.Number);
                        if (!isContains)
                        {
                            result.PaymentOrderState = stateDeny;
                            result.Reason = "Данный счёт не принадлежит заданной организации.";
                            return result;
                        }
                        paymentOrder.Number = (maxNumb.Value + 1);
                        paymentOrder.Id = Guid.NewGuid();
                        paymentOrder.State = stateSucces;
                        das.AddPaymentOrder(paymentOrder);
                        result.PaymentOrderState = stateSucces;
                        result.Reason = "";
                    }
                    else
                    {
                        paymentOrder.Number = (maxNumb.Value + 1);
                        paymentOrder.Id = Guid.NewGuid();
                        paymentOrder.State = stateSucces;
                        das.AddPaymentOrder(paymentOrder);
                        result.PaymentOrderState = stateSucces;
                        result.Reason = "";
                    }
                }
            }
            else
            {
                result.PaymentOrderState = stateDeny;
                result.Reason = "Ошибка проверки ЭЦП";
            }
            return result;
        }

        //Удаление платежного поручения из базы банка
        public State DeletePaymentOrder(PaymentOrder paymentOrder, byte[] eds)
        {
            if (paymentOrder == null)
                throw new ArgumentNullException("paymentOrder");
            if (eds == null)
                throw new ArgumentNullException("eds");

            var das = new DataAccessService();

            if (paymentOrder.PayerUNP <= 0)
                throw new ArgumentException("Wrong payer UNP!");

            var client = das.GetClientByUNP(paymentOrder.PayerUNP);
            if (client == null)
                throw new ApplicationException("Can't find payer info!");

            var ms = new MemoryStream();
            var bf = new BinaryFormatter();
            bf.Serialize(ms, paymentOrder);
            var data = ms.ToArray();

            var hash = MD5Crypto.MD5Encode(data);
            var rsa = new RSASignature();
            var success = rsa.VerifySignature(hash, eds, client.OpenKey);

            if (success)
            {
                var state = das.GetState("удалено");
                das.DeletePaymentOrder(paymentOrder);
                return state;
            }

            return null;
        }

        public IEnumerable<Transaction> GetTransactions(string clientUnp, string depositNumber, DateTime dateFrom, DateTime dateTo)
        {
            var das = new DataAccessService();
            Deposit dep = das.GetDeposit(decimal.Parse(depositNumber));
            IEnumerable<Transaction> trLst = from t in das.GetTransactions() where t.IdPayerDeposit.Equals(dep.Id) select t;

            if (dateFrom != DateTime.MinValue) trLst = (from t in trLst where t.Date >= dateFrom select t);
            if (dateTo != DateTime.MinValue) trLst = (from t in trLst where t.Date <= dateTo select t);

            return trLst.Select(t => new Transaction { Id = t.Id, Date = t.Date, Urgent = t.Urgent, State = t.State, Number = t.Number, Sum = t.Sum }).ToList();
        }

        public IEnumerable<PaymentOrder> GetPaymentOrders(string clientUnp, DateTime dateFrom, DateTime dateTo, int number)
        {
            var das = new DataAccessService();
            IEnumerable<PaymentOrder> poLst = das.GetPaymentOrdersByClientUnp(clientUnp);
            if (number > 0) poLst = (from po in poLst where po.Number == number select po);
            if (dateFrom != DateTime.MinValue) poLst = (from po in poLst where po.Date >= dateFrom select po);
            if (dateTo != DateTime.MinValue) poLst = (from po in poLst where po.Date <= dateTo select po);

            //return poLst.Select(po => new PaymentOrder
            //                              {
            //                                  Id = po.Id,
            //                                  Date = po.Date,
            //                                  Urgent = po.Urgent,
            //                                  State = po.State,
            //                                  Number = po.Number,
            //                                  Sum = po.Sum,
            //                                  Currency = po.Currency
            //                              }).ToList();
            return poLst;
        }

        //Запрос текущего состояния платежного поручения
        public State GetPaymentOrderState(Guid paymentOrderId)
        {
            var das = new DataAccessService();
            PaymentOrder po = das.GetPaymentOrder(paymentOrderId);
            if (po != null)
            {
                return po.State;
            }
            return null;
        }

        //Запрос выписки по счету за определенный период
        public DepositTransactionsHistory GetDepositTransactionsHistory(Guid depositId, DateTime dateFrom, DateTime dateTo)
        {
            var das = new DataAccessService();
            var history = new DepositTransactionsHistory();
            //var transactions = das.GetTransactions();
            var depositLogs = das.GetDepositLogs(depositId);
            decimal initialSum = 0;
            decimal finalSum = 0;

            //foreach (Transaction t in transactions)
            //{
            //    if ((t.IdPayerDeposit == depositId) && (t.Date < dateFrom))
            //        initialSum = initialSum - t.Sum;
            //    if ((t.IdPayerDeposit == depositId) && (t.Date <= dateTo))
            //        finalSum = finalSum - t.Sum;
            //}

            foreach (DepositLog dl in depositLogs)
            {
                if (dl.Date < dateFrom) initialSum = dl.IsRefill ? (initialSum + dl.Sum) : (initialSum - dl.Sum);
                if (dl.Date >= dateFrom && dl.Date <= dateTo)
                {
                    finalSum = dl.IsRefill ? (finalSum + dl.Sum) : (finalSum - dl.Sum);
                    history.DepositLogs.Add(dl);
                }
            }
            finalSum += initialSum;
            //foreach (Transaction t in transactions)
            //    if ((t.IdPayerDeposit == depositId) && (t.Date >= dateFrom) && (t.Date <= dateTo))
            //        history.Transactions.Add(t);

            //foreach (DepositLog dl in depositLogs)
            //{
            //    if ((dl.Date >= dateFrom) && (dl.Date <= dateTo))
            //        history.DepositLogs.Add(dl);
            //}

            history.DepositId = depositId;
            history.DateFrom = dateFrom;
            history.DateTo = dateTo;
            history.InitialSum = initialSum;
            history.FinalSum = finalSum;

            return history;
        }

        //Запрос информации по всем счетам клиента (включая историю транзакций и поступлений/снятия денег с депозита за весь период его существования)
        //public IList<DepositInfo> GetClientDepositsInfo(Guid clientId)
        //{
        //    var das = new DataAccessService();
        //    var deposits = das.GetDeposits(clientId);
        //    var transactions = das.GetTransactions();
        //    var depositLogs = das.GetDepositLogs();
        //    var depositsInfo = new List<DepositInfo>();

        //    foreach (Deposit d in deposits)
        //        if (d.IdClient == clientId)
        //        {
        //            var deposInfo = new DepositInfo { Deposit = d };

        //            foreach (Transaction t in transactions)
        //                if (t.IdPayerDeposit == d.Id)
        //                    deposInfo.Transactions.Add(t);

        //            foreach (DepositLog dl in depositLogs)
        //                if (dl.IdDeposit == d.Id)
        //                    deposInfo.DepositLogs.Add(dl);

        //            depositsInfo.Add(deposInfo);
        //        }

        //    return depositsInfo;
        //}

        public IList<Deposit> GetClientDeposits(Guid idClient)
        {
            var das = new DataAccessService();
            var deposits = das.GetDeposits(idClient);
            return deposits;
        }

        public IList<Partner> GetClientPartners(Guid idClient)
        {
            var das = new DataAccessService();
            var partners = das.GetPartners(idClient);
            return partners;
        }

        //Запрос списка всех банков
        public IList<Bank> GetBanksList()
        {
            var das = new DataAccessService();

            return das.GetBanks();
        }

        public void SavePartner(Partner newPartner)
        {
            var das = new DataAccessService();
            das.AddPartner(newPartner);
        }

        public void UpdatePartner(Partner newPartner)
        {
            var das = new DataAccessService();
            das.UpdatePartner(newPartner);
        }

        public void SavePartnersDeposit(PartnerDeposit newPartnersDeposit)
        {
            var das = new DataAccessService();
            das.AddPartnersDeposit(newPartnersDeposit);
        }

        public void UpdatePartnersDeposit(PartnerDeposit newPartnersDeposit)
        {
            var das = new DataAccessService();
            das.UpdatePartnersDeposit(newPartnersDeposit);
        }

        public int? GetMaxPaymentOrderNumber(decimal unp)
        {
            var das = new DataAccessService();
            return das.GetMaxPaymentOrderNumber(unp);
        }

        //Note: there is the method analog on the ClientWPF side, change both(!)
        private static byte[] GeneratePaymentOrderFieldsHash(PaymentOrder po)
        {
            var sb = new StringBuilder();

            sb.Append(po.Number.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.Date.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.Urgent);
            sb.Append(po.SumCurrencyText);
            sb.Append(po.Currency.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.Sum.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.Payer);
            sb.Append(po.PayerDepositNumber.ToString(CultureInfo.InvariantCulture));
            //miss textEditPayerBank.Text - it's our bank always
            //miss textEditPayerBankCode.Text - it's our bank always
            sb.Append(po.BenefBankName);
            sb.Append(po.BenefBankCode);
            sb.Append(po.Benefeciar);
            sb.Append(po.BenefDepNum.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.PaymentAllocation);
            sb.Append(po.PayerUNP.ToString(CultureInfo.InvariantCulture));
            sb.Append(po.BenefUNP.ToString(CultureInfo.InvariantCulture));

            var encoding = new UTF8Encoding();
            return encoding.GetBytes(sb.ToString());
        }

    }
}